/*
 * MemoryFeeder wraps around agent memory and processes a sequence of traces received from LispTree.
 */
package cz.cuni.amis.pogamut.episodic.lispbots;

import cz.cuni.amis.pogamut.episodic.memory.Affordance;
import cz.cuni.amis.pogamut.episodic.memory.AgentMemory;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Interface that allows others to hook into the parser process and catch
 * individual events. We use two instances of this class: pre and post.
 * All functions return bool. In case the hook fails, the action is not
 * performed or we exit after.
 */
interface MemoryEventHooks {
    boolean startNode(String goal, ArrayList<String> trace, ArrayList<String> aff);
    boolean closeNode(String goal, ArrayList<String> trace);
    boolean executeAction(String action, ArrayList<String> trace, ArrayList<String> aff);
    boolean advanceTime(long time);
    boolean endOfDay();
}

/**
 * @author Zdenek Behan
 */
public class MemoryFeeder implements EventProcessor {
    public AgentMemory mem = null;
    public HashMap methods;
    public HashMap actions;

    private MemoryEventHooks prehooks = null;
    private MemoryEventHooks posthooks = null;

    public MemoryFeeder(AgentMemory _mem, LispTree parser, String fileDescriptorName) {
        mem = _mem;

        // Read the domain descriptor
        processDescriptor(fileDescriptorName);

        // Starting hour (2am), Default saliency (10)
        mem.initialize(2*60*60, 10);

        Iterator<Map.Entry<String,String>> it = methods.entrySet().iterator();
        String s;
        while(it.hasNext()) {
            s = it.next().getKey();

            mem.addGoal(s, 10);
        }

        mem.finalizeInit();
        mem.initVisualizers();
    }

    public MemoryFeeder(AgentMemory _mem, LispTree parser, String fileDescriptorName, MemoryEventHooks _prehooks, MemoryEventHooks _posthooks) {
        this(_mem, parser, fileDescriptorName);

        prehooks = _prehooks;
        posthooks = _posthooks;
    }

    private void processDescriptor(String fileName) {
        String thisLine;
        String[] tempArray;
        ArrayList<String> temp = new ArrayList<String>();
        String name = new String();

        methods = new HashMap();
        actions = new HashMap();

        // Simple state machine: 0 - operators, 1 - list, 2 - methods, 3 - list
        int state = 0;
        try {
            BufferedReader br = new BufferedReader(new FileReader(fileName));
            while ((thisLine = br.readLine()) != null) {
                temp = new ArrayList<String>();

                if (thisLine.equals("")) {
                    continue;
                }
                if (thisLine.startsWith("Operators:")) {
                    state = 1;
                    continue;
                }
                if (thisLine.startsWith("Methods:")) {
                    state = 2;
                    continue;
                }
                if (state == 1) {
                    // Process an operator (atomic action)
                    if (!thisLine.startsWith("!")) {
                        System.err.println("Error: Invalid operator in descriptor.");
                        throw new IOException("Error reading descriptor file.");
                    }
                }
                if (state == 1 || state == 2) {
                    tempArray = thisLine.split(" ");
                    name = tempArray[0];

                    for (int i=1; i<tempArray.length; i++) {
                        if (tempArray[i].startsWith("?")) {
                            temp.add(tempArray[i].substring(1));
                        } else {
                            temp.add(tempArray[i]);
                        }
                    }
                }
                if (state == 1) {
                    actions.put(name, temp);
                    continue;
                }
                if (state == 2) {
                    methods.put(name, temp);
                    continue;
                }

                // Anything here is out of the ordinary
                System.err.println("Error: Unexpected line in descriptor file:");
                System.err.println(thisLine);
                throw new IOException("Error reading descriptor file.");
            } // end while
        } // end try
        catch (IOException e) {
            System.err.println("Error: " + e);
        }
    }

    private ArrayList<Affordance> formatGoalAffordances(String goal, ArrayList<String> affordances) {
        return formatAffordances(goal, methods, affordances);
    }
    private ArrayList<Affordance> formatActionAffordances(String action, ArrayList<String> affordances) {
        return formatAffordances(action, actions, affordances);
    }
    private ArrayList<Affordance> formatAffordances(String nodeName, HashMap map, ArrayList<String> affordances) {
        ArrayList<Affordance> aff = new ArrayList<Affordance>();
        int i = 0;
        String affName, affValue;
        ArrayList<String> affSlots = (ArrayList<String>)map.get(nodeName);

        // Making sure the description was correct.
        assert((affordances.size()) == (affSlots.size()));

        for (i=0; i<affordances.size(); i += 1) { //String oneAff : affordances) {
            // Affordance(type, object)
            affName = affSlots.get(i);
            affValue = affordances.get(i);
            aff.add(new Affordance(affName, affValue));
        }

        return aff;
    }

    public boolean openNode(String goal, ArrayList<String> trace, ArrayList<String> aff) {
        if ((prehooks != null) && !prehooks.startNode(goal, trace, aff)) return false;
        mem.openNode(goal, trace, formatGoalAffordances(goal, aff));
        if ((posthooks != null) && !posthooks.startNode(goal, trace, aff)) return false;
        return true;
    }

    public boolean closeNode(String goal, ArrayList<String> trace) {
        if ((prehooks != null) && !prehooks.closeNode(goal, trace)) return false;
        // We always succeed here
        mem.closeNode(goal, trace, true);
        if ((posthooks != null) && !posthooks.closeNode(goal, trace)) return false;
        return true;
    }

    public boolean atomicAction(String action, ArrayList<String> trace, ArrayList<String> aff) {
        int ret;
        // Each atomic action adds this much.
        if(action.startsWith("!LISPBOTS")) {
            if(action.equals("!LISPBOTS-4HOURS")) {
                if ((prehooks != null) && !prehooks.advanceTime(4*60*60)) return false;
                mem.advanceHour(4);
                if ((posthooks != null) && !posthooks.advanceTime(4*60*60)) return false;

            }
            if(action.equals("!LISPBOTS-SLEEP")) {
                if ((prehooks != null) && !prehooks.endOfDay()) return false;
                mem.performSleep();
                if ((posthooks != null) && !posthooks.endOfDay()) return false;
            }
        } else {
            if ((prehooks != null) && !prehooks.executeAction(action, trace, aff)) return false;
            mem.executeAction(action, trace, formatActionAffordances(action, aff));
            if ((posthooks != null) && !posthooks.executeAction(action, trace, aff)) return false;
        }
        return true;
    }
}
